from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QSplitter,
                             QTextEdit, QTreeWidget, QTreeWidgetItem, QPushButton,
                             QLabel, QLineEdit, QComboBox, QSpinBox, QGroupBox,
                             QFormLayout, QMessageBox, QInputDialog, QTabWidget,
                             QDialog)
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
from PyQt6.QtGui import QFont, QSyntaxHighlighter, QTextCharFormat, QColor
from typing import Optional, List
from loguru import logger
import re

from backend.models import ScriptModel, AssertionModel
from backend.script_generator import ScriptGenerator


class PythonSyntaxHighlighter(QSyntaxHighlighter):
    """Python语法高亮器"""
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.highlighting_rules = []
        
        # 关键字格式
        keyword_format = QTextCharFormat()
        keyword_format.setColor(QColor(85, 85, 255))
        keyword_format.setFontWeight(QFont.Weight.Bold)
        
        keywords = [
            'and', 'as', 'assert', 'break', 'class', 'continue', 'def',
            'del', 'elif', 'else', 'except', 'exec', 'finally', 'for',
            'from', 'global', 'if', 'import', 'in', 'is', 'lambda',
            'not', 'or', 'pass', 'print', 'raise', 'return', 'try',
            'while', 'with', 'yield', 'async', 'await'
        ]
        
        for word in keywords:
            pattern = f'\\b{word}\\b'
            self.highlighting_rules.append((re.compile(pattern), keyword_format))
        
        # 字符串格式
        string_format = QTextCharFormat()
        string_format.setColor(QColor(0, 128, 0))
        self.highlighting_rules.append((re.compile(r'".*?"'), string_format))
        self.highlighting_rules.append((re.compile(r"'.*?'"), string_format))
        
        # 注释格式
        comment_format = QTextCharFormat()
        comment_format.setColor(QColor(128, 128, 128))
        self.highlighting_rules.append((re.compile(r'#[^\n]*'), comment_format))
        
        # 函数格式
        function_format = QTextCharFormat()
        function_format.setColor(QColor(255, 127, 0))
        function_format.setFontWeight(QFont.Weight.Bold)
        self.highlighting_rules.append((re.compile(r'\bdef\s+(\w+)'), function_format))
    
    def highlightBlock(self, text):
        """高亮文本块"""
        for pattern, format in self.highlighting_rules:
            for match in pattern.finditer(text):
                start = match.start()
                length = match.end() - start
                self.setFormat(start, length, format)


class ScriptEditorWidget(QWidget):
    """脚本编辑器组件"""
    
    # 信号定义
    script_changed = pyqtSignal()  # 脚本内容改变信号
    
    def __init__(self):
        super().__init__()
        self.current_script: Optional[ScriptModel] = None
        self.script_generator = ScriptGenerator()
        self.is_modified = False
        
        self.init_ui()
        self.setup_connections()
    
    def init_ui(self):
        """初始化界面"""
        layout = QVBoxLayout(self)
        
        # 创建分割器
        splitter = QSplitter(Qt.Orientation.Horizontal)
        layout.addWidget(splitter)
        
        # 左侧：脚本信息和断言管理
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)
        
        # 脚本信息
        info_group = QGroupBox("脚本信息")
        info_layout = QFormLayout(info_group)
        
        self.name_input = QLineEdit()
        self.name_input.setPlaceholderText("输入脚本名称")
        info_layout.addRow("名称:", self.name_input)
        
        self.description_input = QTextEdit()
        self.description_input.setMaximumHeight(60)
        self.description_input.setPlaceholderText("输入脚本描述")
        info_layout.addRow("描述:", self.description_input)
        
        self.status_combo = QComboBox()
        self.status_combo.addItems(["草稿", "激活", "禁用", "归档"])
        info_layout.addRow("状态:", self.status_combo)
        
        self.browser_combo = QComboBox()
        self.browser_combo.addItems(["Chromium", "Firefox", "WebKit"])
        info_layout.addRow("浏览器:", self.browser_combo)
        
        left_layout.addWidget(info_group)
        
        # 断言管理
        assertion_group = QGroupBox("断言管理")
        assertion_layout = QVBoxLayout(assertion_group)
        
        # 断言列表
        self.assertion_tree = QTreeWidget()
        self.assertion_tree.setHeaderLabels(["类型", "选择器", "期望值", "描述"])
        self.assertion_tree.setRootIsDecorated(False)
        assertion_layout.addWidget(self.assertion_tree)
        
        # 断言操作按钮
        assertion_buttons = QHBoxLayout()
        self.add_assertion_btn = QPushButton("添加断言")
        self.edit_assertion_btn = QPushButton("编辑断言")
        self.remove_assertion_btn = QPushButton("删除断言")
        
        assertion_buttons.addWidget(self.add_assertion_btn)
        assertion_buttons.addWidget(self.edit_assertion_btn)
        assertion_buttons.addWidget(self.remove_assertion_btn)
        assertion_layout.addLayout(assertion_buttons)
        
        left_layout.addWidget(assertion_group)
        
        splitter.addWidget(left_widget)
        
        # 右侧：代码编辑器标签页
        self.tab_widget = QTabWidget()
        
        # 生成的代码标签页
        self.generated_code_editor = QTextEdit()
        self.generated_code_editor.setFont(QFont("Consolas", 10))
        self.generated_code_editor.setReadOnly(True)
        
        # 应用语法高亮
        self.highlighter = PythonSyntaxHighlighter(self.generated_code_editor.document())
        
        self.tab_widget.addTab(self.generated_code_editor, "生成的代码")
        
        # 自定义代码标签页
        self.custom_code_editor = QTextEdit()
        self.custom_code_editor.setFont(QFont("Consolas", 10))
        self.custom_code_editor.setPlaceholderText("在此编写自定义代码...")
        
        # 为自定义代码编辑器也应用语法高亮
        self.custom_highlighter = PythonSyntaxHighlighter(self.custom_code_editor.document())
        
        self.tab_widget.addTab(self.custom_code_editor, "自定义代码")
        
        splitter.addWidget(self.tab_widget)
        
        # 设置分割器比例
        splitter.setSizes([400, 600])
        
        # 操作按钮
        buttons_layout = QHBoxLayout()
        
        self.generate_code_btn = QPushButton("生成代码")
        self.save_btn = QPushButton("保存")
        self.validate_btn = QPushButton("验证语法")
        
        buttons_layout.addWidget(self.generate_code_btn)
        buttons_layout.addWidget(self.save_btn)
        buttons_layout.addWidget(self.validate_btn)
        buttons_layout.addStretch()
        
        layout.addLayout(buttons_layout)
    
    def setup_connections(self):
        """设置信号连接"""
        # 脚本信息改变
        self.name_input.textChanged.connect(self.on_script_info_changed)
        self.description_input.textChanged.connect(self.on_script_info_changed)
        self.status_combo.currentTextChanged.connect(self.on_script_info_changed)
        self.browser_combo.currentTextChanged.connect(self.on_script_info_changed)
        
        # 代码编辑器改变
        self.custom_code_editor.textChanged.connect(self.on_code_changed)
        
        # 断言操作
        self.add_assertion_btn.clicked.connect(self.add_assertion)
        self.edit_assertion_btn.clicked.connect(self.edit_assertion)
        self.remove_assertion_btn.clicked.connect(self.remove_assertion)
        self.assertion_tree.itemDoubleClicked.connect(self.edit_assertion)
        
        # 按钮操作
        self.generate_code_btn.clicked.connect(self.generate_code)
        self.save_btn.clicked.connect(self.save_script)
        self.validate_btn.clicked.connect(self.validate_syntax)
    
    def load_script(self, script: ScriptModel):
        """加载脚本"""
        try:
            self.current_script = script
            
            # 加载脚本信息
            self.name_input.setText(script.name)
            self.description_input.setPlainText(script.description or "")
            
            # 设置状态
            status_map = {"draft": 0, "active": 1, "disabled": 2, "archived": 3}
            self.status_combo.setCurrentIndex(status_map.get(script.status.value, 0))
            
            # 设置浏览器
            browser_map = {"chromium": 0, "firefox": 1, "webkit": 2}
            self.browser_combo.setCurrentIndex(browser_map.get(script.browser_type.value, 0))
            
            # 加载断言
            self.load_assertions()
            
            # 加载代码
            if script.generated_code:
                self.generated_code_editor.setPlainText(script.generated_code)
            else:
                self.generated_code_editor.clear()
            
            # 清空自定义代码编辑器（或加载自定义代码）
            self.custom_code_editor.clear()
            
            self.is_modified = False
            logger.info(f"已加载脚本: {script.name}")
            
        except Exception as e:
            logger.error(f"加载脚本失败: {e}")
            QMessageBox.critical(self, "错误", f"加载脚本失败: {str(e)}")
    
    def load_assertions(self):
        """加载断言列表"""
        self.assertion_tree.clear()
        
        if not self.current_script:
            return
        
        for assertion in self.current_script.assertions:
            item = QTreeWidgetItem()
            item.setText(0, assertion.type)
            item.setText(1, assertion.selector)
            item.setText(2, assertion.expected_value)
            item.setText(3, assertion.description or "")
            item.setData(0, Qt.ItemDataRole.UserRole, assertion.id)
            self.assertion_tree.addTopLevelItem(item)
    
    def on_script_info_changed(self):
        """脚本信息改变"""
        if self.current_script:
            self.is_modified = True
            self.script_changed.emit()
    
    def on_code_changed(self):
        """代码改变"""
        if self.current_script:
            self.is_modified = True
            self.script_changed.emit()
    
    def add_assertion(self):
        """添加断言"""
        if not self.current_script:
            QMessageBox.warning(self, "警告", "请先选择一个脚本")
            return
        
        dialog = AssertionDialog(self)
        if dialog.exec() == AssertionDialog.DialogCode.Accepted:
            assertion = dialog.get_assertion()
            self.current_script.add_assertion(assertion)
            self.load_assertions()
            self.is_modified = True
            self.script_changed.emit()
    
    def edit_assertion(self):
        """编辑断言"""
        current_item = self.assertion_tree.currentItem()
        if not current_item:
            QMessageBox.warning(self, "警告", "请先选择要编辑的断言")
            return
        
        assertion_id = current_item.data(0, Qt.ItemDataRole.UserRole)
        assertion = None
        
        for a in self.current_script.assertions:
            if a.id == assertion_id:
                assertion = a
                break
        
        if not assertion:
            return
        
        dialog = AssertionDialog(self, assertion)
        if dialog.exec() == AssertionDialog.DialogCode.Accepted:
            updated_assertion = dialog.get_assertion()
            # 更新断言
            assertion.type = updated_assertion.type
            assertion.selector = updated_assertion.selector
            assertion.expected_value = updated_assertion.expected_value
            assertion.description = updated_assertion.description
            
            self.load_assertions()
            self.is_modified = True
            self.script_changed.emit()
    
    def remove_assertion(self):
        """删除断言"""
        current_item = self.assertion_tree.currentItem()
        if not current_item:
            QMessageBox.warning(self, "警告", "请先选择要删除的断言")
            return
        
        assertion_id = current_item.data(0, Qt.ItemDataRole.UserRole)
        
        reply = QMessageBox.question(
            self, "确认删除", "确定要删除这个断言吗？",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        
        if reply == QMessageBox.StandardButton.Yes:
            if self.current_script.remove_assertion(assertion_id):
                self.load_assertions()
                self.is_modified = True
                self.script_changed.emit()
    
    def generate_code(self):
        """生成代码"""
        if not self.current_script:
            QMessageBox.warning(self, "警告", "请先选择一个脚本")
            return
        
        try:
            # 从动作生成基础代码
            if self.current_script.actions:
                actions_data = [action.dict() for action in self.current_script.actions]
                script_code = self.script_generator.generate_script(actions_data, self.current_script.name)
                
                # 添加断言
                for assertion in self.current_script.assertions:
                    script_code = self.script_generator.add_assertion(
                        script_code,
                        assertion.type,
                        assertion.selector,
                        assertion.expected_value,
                        assertion.line_number
                    )
                
                self.generated_code_editor.setPlainText(script_code)
                self.current_script.generated_code = script_code
                self.is_modified = True
                self.script_changed.emit()
            else:
                QMessageBox.information(self, "提示", "脚本没有动作，无法生成代码")
                
        except Exception as e:
            logger.error(f"生成代码失败: {e}")
            QMessageBox.critical(self, "错误", f"生成代码失败: {str(e)}")
    
    def save_script(self):
        """保存脚本"""
        if not self.current_script:
            return
        
        try:
            # 更新脚本信息
            self.current_script.name = self.name_input.text().strip()
            self.current_script.description = self.description_input.toPlainText().strip() or None
            
            # 更新状态
            status_map = {0: "draft", 1: "active", 2: "disabled", 3: "archived"}
            from backend.models import ScriptStatus
            status_value = status_map.get(self.status_combo.currentIndex(), "draft")
            self.current_script.status = ScriptStatus(status_value)
            
            # 更新浏览器
            browser_map = {0: "chromium", 1: "firefox", 2: "webkit"}
            from backend.models import BrowserType
            browser_value = browser_map.get(self.browser_combo.currentIndex(), "chromium")
            self.current_script.browser_type = BrowserType(browser_value)
            
            # 更新生成的代码
            self.current_script.generated_code = self.generated_code_editor.toPlainText()
            
            self.is_modified = False
            logger.info(f"脚本已保存: {self.current_script.name}")
            QMessageBox.information(self, "成功", "脚本已保存")
            
        except Exception as e:
            logger.error(f"保存脚本失败: {e}")
            QMessageBox.critical(self, "错误", f"保存脚本失败: {str(e)}")
    
    def validate_syntax(self):
        """验证语法"""
        code = ""
        if self.tab_widget.currentWidget() == self.generated_code_editor:
            code = self.generated_code_editor.toPlainText()
        else:
            code = self.custom_code_editor.toPlainText()
        
        if not code.strip():
            QMessageBox.warning(self, "警告", "代码为空")
            return
        
        try:
            from backend.executor import ScriptValidator
            result = ScriptValidator.validate_script_syntax(code)
            
            if result["valid"]:
                QMessageBox.information(self, "验证结果", "语法正确")
            else:
                QMessageBox.warning(self, "语法错误", result["error"])
                
        except Exception as e:
            QMessageBox.critical(self, "错误", f"验证失败: {str(e)}")
    
    def get_current_script(self) -> Optional[ScriptModel]:
        """获取当前脚本"""
        if self.current_script and self.is_modified:
            # 更新脚本信息
            self.current_script.name = self.name_input.text().strip()
            self.current_script.description = self.description_input.toPlainText().strip() or None
            self.current_script.generated_code = self.generated_code_editor.toPlainText()
        
        return self.current_script


class AssertionDialog(QDialog):
    """断言编辑对话框"""
    
    def __init__(self, parent=None, assertion: AssertionModel = None):
        super().__init__(parent)
        self.assertion = assertion
        self.setWindowTitle("断言设置")
        self.setModal(True)
        self.resize(400, 300)
        self._result = QDialog.DialogCode.Rejected
        
        self.init_ui()
        
        if assertion:
            self.load_assertion(assertion)
    
    def init_ui(self):
        """初始化界面"""
        layout = QVBoxLayout(self)
        
        form_layout = QFormLayout()
        
        # 断言类型
        self.type_combo = QComboBox()
        self.type_combo.addItems([
            "text_equals",      # 文本等于
            "text_contains",    # 文本包含
            "element_visible",  # 元素可见
            "element_hidden",   # 元素隐藏
            "url_equals",       # URL等于
            "url_contains"      # URL包含
        ])
        form_layout.addRow("断言类型:", self.type_combo)
        
        # 选择器
        self.selector_input = QLineEdit()
        self.selector_input.setPlaceholderText("CSS选择器或XPath")
        form_layout.addRow("选择器:", self.selector_input)
        
        # 期望值
        self.expected_value_input = QLineEdit()
        self.expected_value_input.setPlaceholderText("期望的值")
        form_layout.addRow("期望值:", self.expected_value_input)
        
        # 描述
        self.description_input = QLineEdit()
        self.description_input.setPlaceholderText("断言描述（可选）")
        form_layout.addRow("描述:", self.description_input)
        
        layout.addLayout(form_layout)
        
        # 按钮
        buttons_layout = QHBoxLayout()
        self.ok_btn = QPushButton("确定")
        self.cancel_btn = QPushButton("取消")
        
        buttons_layout.addWidget(self.ok_btn)
        buttons_layout.addWidget(self.cancel_btn)
        layout.addLayout(buttons_layout)
        
        # 连接信号
        self.ok_btn.clicked.connect(self.accept)
        self.cancel_btn.clicked.connect(self.reject)
    
    def load_assertion(self, assertion: AssertionModel):
        """加载断言数据"""
        # 设置断言类型
        index = self.type_combo.findText(assertion.type)
        if index >= 0:
            self.type_combo.setCurrentIndex(index)
        
        self.selector_input.setText(assertion.selector)
        self.expected_value_input.setText(assertion.expected_value)
        self.description_input.setText(assertion.description or "")
    
    def get_assertion(self) -> AssertionModel:
        """获取断言数据"""
        assertion = AssertionModel(
            type=self.type_combo.currentText(),
            selector=self.selector_input.text().strip(),
            expected_value=self.expected_value_input.text().strip(),
            description=self.description_input.text().strip() or None
        )
        
        if self.assertion:
            assertion.id = self.assertion.id
        
        return assertion
    
    def accept(self):
        """确定按钮"""
        if not self.selector_input.text().strip():
            QMessageBox.warning(self, "警告", "请输入选择器")
            return
        
        if not self.expected_value_input.text().strip() and self.type_combo.currentText() not in ["element_visible", "element_hidden"]:
            QMessageBox.warning(self, "警告", "请输入期望值")
            return
        
        self._result = QDialog.DialogCode.Accepted
        super().accept()
        
    def reject(self):
        """取消按钮"""
        self._result = QDialog.DialogCode.Rejected
        super().reject()
    
    class DialogCode:
        Accepted = 1
        Rejected = 0